#include "stdafx.h"
#include "Object.h"
#include "Scene.h"
#include "PointLight.h"

namespace PhotonMapping
{

//*****************************************************************************
// class overview:
//   Helper class for building the projection map from all the objects in the
//   scene. 
//   Code parts taken from http://www.sfu.ca/~pkarimia/courses/cmpt770graphics/lab4/
//-----------------------------------------------------------------------------
class ProjectMapBuilder
{
	public:
		// takes a reference to a vectore where the project map should be stored to
		ProjectMapBuilder(const Math::Vector3& p_LightPos, std::vector<uintx>& p_ProjMap, uintx p_PMRes) 
			: m_LightPos(p_LightPos), m_ProjMap(p_ProjMap), m_PMRes(p_PMRes)
		{

		}

		// builds the projection map
		void operator()(const ObjectPtr& p_Ptr)
		{
			using namespace Math;

		}

	private:
		// projection map
		std::vector<uintx>& m_ProjMap;

		// projection map resolution
		const uintx m_PMRes;

		// light position
		const Math::Vector3 m_LightPos;
};




PointLight::PointLight(const Math::Vector3& p_Pos, const Color& p_Color, const float p_Intensity) 
	: ILight(p_Pos, p_Color, p_Intensity)
{
	m_UsePM = false;
}

void PointLight::CreateProjectionMap(const ScenePtr p_Ptr)
{
	using namespace std;
	using namespace Math;

	// create projection map from all objects in the scene
	std::vector<uintx> projMap(m_PMRes * m_PMRes, 0);
	for(vector<ObjectPtr>::const_iterator i = p_Ptr->GetObjects().begin(); i != p_Ptr->GetObjects().end(); ++i)
	{
		// skip if object is not specular or 
		MaterialPtr curMat = p_Ptr->GetMaterials()[(*i)->GetMaterial()];
		if(!(curMat->GetRefraction().Sum() > 0.0f))
			continue;

		Sphere boundingSphere = (*i)->GetBoundingSphere();

		/// finding u1 & u2 that point to the object center		
		Vector3 w = boundingSphere.m_Center - GetPosition();
		w.Normalize();
		/// This is the inverse of sampling function
		float wu1 = (1.0f - w.m_Z) / 2.0f;
		float wu2 = atan2f(w.m_Y, w.m_X) * Inv2PI;
		wu2= (wu2 < 0.0f) ? (wu2 + 1.0f) : (wu2);

		int wu1_cell=int(wu1 * m_PMRes);
		int wu2_cell=int(wu2 * m_PMRes);
		projMap[wu1_cell + wu2_cell * m_PMRes] = 1;

		/// Check all other cells for non-empty cells
		float cellSize = 1.0f / m_PMRes;
		for (int j=0; j < m_PMRes; j++)
		{
			for (int i=0; i < m_PMRes; i++)
			{
				float u1 = static_cast<float>(i) * cellSize;
				float u2 = static_cast<float>(j) * cellSize;

				uintx in=(i+1) % m_PMRes;
				uintx jn=(j+1) % m_PMRes;
				uintx ip=(i+m_PMRes-1) % m_PMRes;
				uintx jp=(j+m_PMRes-1) % m_PMRes;

				Vector3 d;
				float l;

				/// finding distance from the current u1 & u2 to the 
				/// object center
				d = UniformSampleSphere(u1, u2);
				l = Cross(d, GetPosition() - boundingSphere.m_Center).Length();
				float ld = d * (boundingSphere.m_Center - GetPosition());
				if((l < boundingSphere.m_Radius) && (ld > 0.f))
				{
					/// if any of the four point around a cell intersects with
					/// the spherical bound that cell will be marked as full 
					projMap[i+j*m_PMRes]=true;
					projMap[ip+j*m_PMRes]=true;
					projMap[i+jp*m_PMRes]=true;
					projMap[in+j*m_PMRes]=true;
					projMap[i+jn*m_PMRes]=true;
					projMap[ip+jp*m_PMRes]=true;
					projMap[in+jp*m_PMRes]=true;
					projMap[ip+jn*m_PMRes]=true;
					projMap[in+jn*m_PMRes]=true;
				}
			}
		} 
	}

	// transform projection map into our project map U/V cells arrays
	m_PMCellsU.reserve(projMap.size());
	m_PMCellsV.reserve(projMap.size());
	for(int j = 0; j < m_PMRes; ++j) 
	{
		for(int i = 0; i < m_PMRes; ++i) 
		{
			if(projMap[i + j * m_PMRes]) 
			{
				m_PMCellsU.push_back(static_cast<float>(i) / static_cast<float>(m_PMRes));
				m_PMCellsV.push_back(static_cast<float>(j) / static_cast<float>(m_PMRes));
			}
		}
	}	

	m_UsePM = true;
}

const Math::Ray PointLight::SampleLightRay(float p_U1, float p_U2)
{
	using namespace Math;

	Ray lightRay;
	lightRay.m_Position = GetPosition();
	if(m_UsePM)
	{
		/// multipling u1 by number of non-empty cells and 
		/// using the real part as the cell number and the floating points
		/// as u1 for UniformSampling:
		///
		int cellNumber = static_cast<int>(p_U1 * m_PMCellsU.size());
		p_U1 = p_U1 * m_PMCellsU.size() - cellNumber;

		/// Mapping u1 & u2 from [0,1) to the corressponding cell range
		p_U1 = m_PMCellsU[cellNumber] + p_U1 / static_cast<float>(m_PMRes);
		p_U2 = m_PMCellsV[cellNumber] + p_U2 / static_cast<float>(m_PMRes);
		lightRay.m_Direction = UniformSampleSphere(p_U1, p_U2);
	}
	else
	{
		do
		{
			lightRay.m_Direction.m_X = Random(2.0f) - 1.0f;
			lightRay.m_Direction.m_Y = Random(2.0f) - 1.0f;
			lightRay.m_Direction.m_Z = Random(2.0f) - 1.0f;
		}
		while(Sqr(lightRay.m_Direction.m_X) + Sqr(lightRay.m_Direction.m_Y) + Sqr(lightRay.m_Direction.m_Z) > 1.0f);
	}

	return lightRay;
}

const Color PointLight::GetMaxFlux()
{
	return GetColor() * GetIntensity() * 2.0f * Math::PI;
}

} // PhotonMapping
